/*
 * @(#) $Header$
 *
 * Copyright (C)  2006  Daniel Léonard
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package ca.forklabs.baselib.util.function;

import ca.forklabs.baselib.util.BinaryFunction;
import ca.forklabs.baselib.util.UnaryFunction;

/**
 * Class {@code BinaryCompose} is an {@link UnaryFunction} that, given two
 * {@code UnaryFunction}s <em>g1(x)</em> and <em>g2(x)</em> and a
 * {@link BinaryFunction} <em>f(y, z)</em>, performs <em>f(g1(x), g2(x))</em>.
 * The unary functions should not change their parameters, but there are no
 * implemented means to prevent modifications.
 *
 * @param   <R>   the type of the returned object.
 * @param   <I1>   the type of the first argument object to <em>f(y, z)</em>.
 * @param   <I2>   the type of the second argument object to <em>f(y, z)</em>.
 * @param   <A>   the type of the argument to <em>g1(x)</em> and
 *                <em>g2(x)</em>.
 *
 * @author <a href="mailto:forklabs at gmail.com?subject=ca.forklabs.baselib.util.function.BinaryCompose">Daniel Léonard</a>
 * @version $Revision$
 */
public class BinaryCompose<R, I1, I2, A> implements UnaryFunction<R, A> {

//---------------------------
// Instance variable
//---------------------------

   /** The binary function. */
   private BinaryFunction<R, I1, I2> f;

   /** The first unary function. */
   private UnaryFunction<I1, A> g1;

   /** The second unary function. */
   private UnaryFunction<I2, A> g2;


//---------------------------
// Constructor
//---------------------------

   /**
    * Constructor.
    * @param   f   the binary function.
    * @param   g1   the first unary function.
    * @param   g2   the second unary function.
    * @exception   NullPointerException   if any parameter is {@code null}.
    */
   public BinaryCompose(BinaryFunction<R, I1, I2> f, UnaryFunction<I1, A> g1, UnaryFunction<I2, A> g2) {
      this.setF(f);
      this.setG1(g1);
      this.setG2(g2);
      }


//---------------------------
// Accessor and mutator
//---------------------------

   /**
    * Changes the binary function.
    * @param   f   the new binary function.
    * @exception   NullPointerException   if the function is <code>null</code>.
    */
   @SuppressWarnings("nls")
   protected void setF(BinaryFunction<R, I1, I2> f) {
      if (null == f) {
         String message = this.getNullFunctionErrorMessage("f(y, z)");
         throw new NullPointerException(message);
         }
      this.f = f;
      }

   /**
    * Returns the binary function.
    * @return   the binary function.
    */
   protected BinaryFunction<R, I1, I2> getF() {
      return this.f;
      }

   /**
    * Changes the first unary function.
    * @param   g1   the new first unary function.
    * @exception   NullPointerException   if the function is <code>null</code>.
    */
   @SuppressWarnings("nls")
   protected void setG1(UnaryFunction<I1, A> g1) {
      if (null == g1) {
         String message = this.getNullFunctionErrorMessage("g1(x)");
         throw new NullPointerException(message);
         }
      this.g1 = g1;
      }

   /**
    * Returns the first unary function.
    * @return   the first unary function.
    */
   protected UnaryFunction<I1, A> getG1() {
      return this.g1;
      }

   /**
    * Changes the second unary function.
    * @param   g2   the new second unary function.
    * @exception   NullPointerException   if the function is <code>null</code>.
    */
   @SuppressWarnings("nls")
   protected void setG2(UnaryFunction<I2, A> g2) {
      if (null == g2) {
         String message = this.getNullFunctionErrorMessage("g2(x)");
         throw new NullPointerException(message);
         }
      this.g2 = g2;
      }

   /**
    * Returns the second unary function.
    * @return   the second unary function.
    */
   protected UnaryFunction<I2, A> getG2() {
      return this.g2;
      }


//---------------------------
// Implemented method from ca.forklabs.baselib.util.UnaryFunction
//---------------------------

   /**
    * Invokes the composition on the given object. It first invoke
    * <em>g1(x)</em>, then <em>g2(x)</em>. Finally it invokes <em>f(y, z)</em>
    * with the results of the unary functions.
    * @param  arg   the argument.
    * @return   the result of the composition.
    */
   @Override
   public R invoke(A arg) {
      UnaryFunction<I1, A> g1_of_x = this.getG1();
      I1 intermediate1 = g1_of_x.invoke(arg);

      UnaryFunction<I2, A> g2_of_x = this.getG2();
      I2 intermediate2 = g2_of_x.invoke(arg);

      BinaryFunction<R, I1, I2> f_of_y_z = this.getF();
      R result = f_of_y_z.invoke(intermediate1, intermediate2);

      return result;
      }


//---------------------------
// Instance methods
//---------------------------

   /**
    * Gets the error message saying that a function is {@code null}.
    * @param   function   the null function.
    * @return   the error message.
    */
   protected String getNullFunctionErrorMessage(String function) {
      String key = Resources.COMPOSE_NULL_FUNCTION;
      String message = Resources.getLocalizedString(key, function);
      return message;
      }

   }
